package open.douban.hashtree;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * a sync using hash tree similar to beansdb:
 * http://code.google.com/p/beansdb/source/browse/trunk/python/sync.py
 */
public class HashTreeMirror {
	private static int dir_flag = -1;

	/**
	 * store interface
	 */
	public static interface HTStore {
		String listDir(String path) throws IOException;

		void set(int key, Object o) throws IOException;

		void set(int key, Object old, Object _new) throws IOException;

		Object get(int key) throws IOException;
	}

	public static void sync(HTStore h1, HTStore h2) throws IOException {
		mirror(h1, h2, ""); // h1 -> h2
		mirror(h2, h1, ""); // h2 -> h1
	}

	private static void mirror(HTStore src, HTStore dest, String path)
			throws IOException {
		String s1 = src.listDir(path);
		String s2 = dest.listDir(path);
		if (s1.equals(s2))
			return;

		Map<Integer, Pair> srcMap = parseStatus(s1);
		Map<Integer, Pair> destMap = parseStatus(s2);

		if (srcMap.containsKey(dir_flag)) {
			for (int i = 0; i < 16; i++) {
				Pair p1 = srcMap.get(i);
				Pair p2 = destMap.get(i);
				if (!p1.equals(p2)) {
					mirror(src, dest, path + HashTree1.int2hex[i]);
				}
			}
		} else if (destMap.containsKey(dir_flag)) {
			for (int i = 0; i < 16; i++) {
				mirror(dest, src, path + HashTree1.int2hex[i]);
			}
		} else if (!srcMap.containsKey(dir_flag)
				&& !destMap.containsKey(dir_flag)) {
			syncItem(src, dest, srcMap, destMap);
		}
	}

	private static void syncItem(HTStore src, HTStore dest,
			Map<Integer, Pair> srcMap, Map<Integer, Pair> destMap)
			throws IOException {
		Iterator<Integer> it = srcMap.keySet().iterator();
		while (it.hasNext()) {
			Integer key = it.next();
			Pair v = srcMap.get(key);
			Pair v2 = destMap.get(key);
			if (v2 == null) {
				System.out.println(key + " " + src + " --> " + dest);
				Object o = src.get(key);
				dest.set(key, o);
			} else if (v.hash_v > v2.hash_v) {
				// TODO
				Object _new = src.get(key);
				Object old = dest.get(key);
				dest.set(key, old, _new);
			}
		}
	}

	private static Map<Integer, Pair> parseStatus(String s) {
		HashMap<Integer, Pair> map = new HashMap<Integer, Pair>();

		String[] split = s.split(HashTree1.LINE);
		for (int i = 0; i < split.length; i++) {
			String[] ss = split[i].split(HashTree1.BLANK);

			if (ss.length == 3) {
				Pair p = new Pair();
				p.hash_v = Integer.parseInt(ss[1]); // hash_v
				p.item_cnt = Integer.parseInt(ss[2]); // item_cnt
				map.put(i, p);
				map.put(dir_flag, null);
			} else {
				Pair p = new Pair();
				p.hash_v = Integer.parseInt(ss[1]); // hash_v
				p.item_cnt = 1; // item_cnt is 1 for single item
				map.put(Integer.parseInt(ss[0]), p);
			}
		}

		return map;
	}

	/* *********************************************************
	 * 
	 * Inner class
	 * 
	 * *******************************************************
	 */
	private static class Pair {
		int hash_v;
		int item_cnt;

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + hash_v;
			result = prime * result + item_cnt;
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			Pair other = (Pair) obj;
			if (hash_v != other.hash_v)
				return false;
			if (item_cnt != other.item_cnt)
				return false;
			return true;
		}
	}
}
